/*! \file
 * \brief Enter a brief description here.
 *
 * Enter a detailed description here. (optional)
 * \author Author (Ivo Wingelaar)
 * \date Date (2011)
 * \version Version (0.0.1)
 * \copyright Copyright (GNU LGPL v3)
 */

#ifndef __LYNX_MATERIAL_RENDERER_C_HPP_INCLUDED__
#define __LYNX_MATERIAL_RENDERER_C_HPP_INCLUDED__

#include "LynxMaterialRenderer.hpp"

namespace Lynx
{

class VideoManager_C;

//! Base class for all internal OpenGL material renderers
class MaterialRenderer_C : public MaterialRenderer
{
public:
    //! Constructor
    MaterialRenderer_C(VideoManager_C* driver) :
        mDriver(driver)
    {
        //
    }

protected:
    VideoManager_C* mDriver;
};

//! Solid material renderer
class MaterialRenderer_SOLID : public MaterialRenderer_C
{
public:
    MaterialRenderer_SOLID(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(1);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(resetAllRenderstates || (material.mMaterialType != lastMaterial.mMaterialType))
        {
            // thanks to Murphy, the following line removed some
            // bugs with several OpenGL implementations.
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        }
    }
};

//! Generic Texture Blend
class MaterialRenderer_ONETEXTURE_BLEND : public MaterialRenderer_C
{
public:

    MaterialRenderer_ONETEXTURE_BLEND(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(1);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        E_BLEND_FACTOR srcFact,dstFact;
        E_MODULATE_FUNC modulate;
        uint32_t alphaSource;
        unpack_texture_blend_func(srcFact, dstFact, modulate, alphaSource, material.mMaterialTypeParam);

#ifdef GL_ARB_texture_env_combine
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
        glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
        glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
        glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB);
        glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, (float) modulate);
#else
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
        glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
        glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
        glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PREVIOUS_EXT);
        glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, (float) modulate);
#endif

        glBlendFunc(getGLBlend(srcFact), getGLBlend(dstFact));
        glEnable(GL_ALPHA_TEST);
        glAlphaFunc(GL_GREATER, 0.f);
        glEnable(GL_BLEND);

        if(texture_blend_func_has_alpha(srcFact) || texture_blend_func_has_alpha(dstFact))
        {
            if(alphaSource==EAS_VERTEX_COLOR)
            {
#ifdef GL_ARB_texture_env_combine
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB);
#else
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_PRIMARY_COLOR_EXT);
#endif
            }
            else if(alphaSource==EAS_TEXTURE)
            {
#ifdef GL_ARB_texture_env_combine
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
#else
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE);
#endif
            }
            else
            {
#ifdef GL_ARB_texture_env_combine
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PRIMARY_COLOR_ARB);
#else
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, GL_PRIMARY_COLOR_EXT);
#endif
            }

#ifdef GL_ARB_texture_env_combine
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB);
#else
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT);
#endif
        }
    }

    virtual void on_unset_material()
    {
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#ifdef GL_ARB_texture_env_combine
        glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
        glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.f);
        glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB);
#else
        glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE);
        glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.f);
        glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PREVIOUS_EXT);
#endif

        glDisable(GL_BLEND);
        glDisable(GL_ALPHA_TEST);
    }

    //! Returns if the material is transparent.
    /** Is not always transparent, but mostly. */
    virtual bool is_transparent() const
    {
        return true;
    }

private:

    uint32_t getGLBlend(E_BLEND_FACTOR factor) const
    {
        uint32_t r = 0;
        switch(factor)
        {
        case EBF_ZERO:
            r = GL_ZERO;
            break;
        case EBF_ONE:
            r = GL_ONE;
            break;
        case EBF_DST_COLOR:
            r = GL_DST_COLOR;
            break;
        case EBF_ONE_MINUS_DST_COLOR:
            r = GL_ONE_MINUS_DST_COLOR;
            break;
        case EBF_SRC_COLOR:
            r = GL_SRC_COLOR;
            break;
        case EBF_ONE_MINUS_SRC_COLOR:
            r = GL_ONE_MINUS_SRC_COLOR;
            break;
        case EBF_SRC_ALPHA:
            r = GL_SRC_ALPHA;
            break;
        case EBF_ONE_MINUS_SRC_ALPHA:
            r = GL_ONE_MINUS_SRC_ALPHA;
            break;
        case EBF_DST_ALPHA:
            r = GL_DST_ALPHA;
            break;
        case EBF_ONE_MINUS_DST_ALPHA:
            r = GL_ONE_MINUS_DST_ALPHA;
            break;
        case EBF_SRC_ALPHA_SATURATE:
            r = GL_SRC_ALPHA_SATURATE;
            break;
        }
        return r;
    }

};

//! Solid 2 layer material renderer
class MaterialRenderer_SOLID_2_LAYER : public MaterialRenderer_C
{
public:

    MaterialRenderer_SOLID_2_LAYER(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(2);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(material.mMaterialType != lastMaterial.mMaterialType || resetAllRenderstates)
        {
            if(mDriver->query_feature(EVMF_MULTITEXTURE))
            {
                mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
#ifdef GL_ARB_texture_env_combine
                glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB);
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE2_RGB_ARB, GL_PRIMARY_COLOR);
                glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA);
#else
                glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_PRIMARY_COLOR_EXT);
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_INTERPOLATE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
                glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE2_RGB_EXT, GL_PRIMARY_COLOR);
                glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB_EXT, GL_SRC_ALPHA);
#endif
            }
        }
    }

    virtual void on_unset_material()
    {
        if(mDriver->query_feature(EVMF_MULTITEXTURE))
        {
            mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#ifdef GL_ARB_texture_env_combine
            glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_COLOR);
#else
            glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB_EXT, GL_SRC_COLOR);
#endif
            mDriver->extGlActiveTexture(GL_TEXTURE0_ARB);
        }
    }
};

//! Transparent add color material renderer
class MaterialRenderer_TRANSPARENT_ADD_COLOR : public MaterialRenderer_C
{
public:

    MaterialRenderer_TRANSPARENT_ADD_COLOR(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(1);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if((material.mMaterialType != lastMaterial.mMaterialType) || resetAllRenderstates)
        {
            glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
            glEnable(GL_BLEND);
        }
    }

    virtual void on_unset_material()
    {
        glDisable(GL_BLEND);
    }

    //! Returns if the material is transparent.
    virtual bool is_transparent() const
    {
        return true;
    }
};

//! Transparent vertex alpha material renderer
class MaterialRenderer_TRANSPARENT_VERTEX_ALPHA : public MaterialRenderer_C
{
public:

    MaterialRenderer_TRANSPARENT_VERTEX_ALPHA(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(1);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(material.mMaterialType != lastMaterial.mMaterialType || resetAllRenderstates)
        {
#ifdef GL_ARB_texture_env_combine
            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
            glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB);
            glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
#else
            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
            glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_PRIMARY_COLOR_EXT);
            glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
#endif
            glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_BLEND);
        }
    }

    virtual void on_unset_material()
    {
        // default values
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#ifdef GL_ARB_texture_env_combine
        glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
        glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
#else
        glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE);
        glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE);
#endif
        glDisable(GL_BLEND);
    }

    //! Returns if the material is transparent.
    virtual bool is_transparent() const
    {
        return true;
    }
};

//! Transparent alpha channel material renderer
class MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL : public MaterialRenderer_C
{
public:

    MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(1);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(material.mMaterialType != lastMaterial.mMaterialType || resetAllRenderstates || material.mMaterialTypeParam != lastMaterial.mMaterialTypeParam)
        {
#ifdef GL_ARB_texture_env_combine
            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
            glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB);
            glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
#else
            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
            glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PREVIOUS_EXT);
            glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
            glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE);
#endif
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_BLEND);
            glEnable(GL_ALPHA_TEST);

            glAlphaFunc(GL_GREATER, material.mMaterialTypeParam);
        }
    }

    virtual void on_unset_material()
    {
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#ifdef GL_ARB_texture_env_combine
        glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
#else
        glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE);
#endif
        glDisable(GL_ALPHA_TEST);
        glDisable(GL_BLEND);
    }

    //! Returns if the material is transparent.
    virtual bool is_transparent() const
    {
        return true;
    }
};

//! Transparent alpha channel material renderer
class MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL_REF : public MaterialRenderer_C
{
public:

    MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL_REF(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(1);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(material.mMaterialType != lastMaterial.mMaterialType || resetAllRenderstates)
        {
            glEnable(GL_ALPHA_TEST);
            glAlphaFunc(GL_GREATER, 0.5f);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        }
    }

    virtual void on_unset_material()
    {
        glDisable(GL_ALPHA_TEST);
    }

    //! Returns if the material is transparent.
    virtual bool is_transparent() const
    {
        return false;  // this material is not really transparent because it does no blending.
    }
};

//! material renderer for all kinds of lightmaps
class MaterialRenderer_LIGHTMAP : public MaterialRenderer_C
{
public:

    MaterialRenderer_LIGHTMAP(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(2);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(material.mMaterialType != lastMaterial.mMaterialType || resetAllRenderstates)
        {
            // diffuse map

            switch(material.mMaterialType)
            {
            case EMT_LIGHTMAP_LIGHTING:
            case EMT_LIGHTMAP_LIGHTING_M2:
            case EMT_LIGHTMAP_LIGHTING_M4:
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
                break;
            case EMT_LIGHTMAP_ADD:
            case EMT_LIGHTMAP:
            case EMT_LIGHTMAP_M2:
            case EMT_LIGHTMAP_M4:
            default:
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
                break;
            }

            if(mDriver->query_feature(EVMF_MULTITEXTURE))
            {
                // lightmap

                mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
#ifdef GL_ARB_texture_env_combine
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);

                if(material.mMaterialType == EMT_LIGHTMAP_ADD)
                    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD);
                else
                    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);

                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PREVIOUS_ARB);
#else
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);

                if(material.mMaterialType == EMT_LIGHTMAP_ADD)
                    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
                else
                    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);

                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
                glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_PREVIOUS_EXT);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, GL_PREVIOUS_EXT);
#endif

                switch(material.mMaterialType)
                {
                case EMT_LIGHTMAP_M4:
                case EMT_LIGHTMAP_LIGHTING_M4:
#ifdef GL_ARB_texture_env_combine
                    glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 4.0f);
#else
                    glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 4.0f);
#endif
                    break;
                case EMT_LIGHTMAP_M2:
                case EMT_LIGHTMAP_LIGHTING_M2:
#ifdef GL_ARB_texture_env_combine
                    glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2.0f);
#else
                    glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f);
#endif
                    break;
                default:
#ifdef GL_ARB_texture_env_combine
                    glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0f);
#else
                    glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f);
#endif
                }
                mDriver->extGlActiveTexture(GL_TEXTURE0_ARB);
            }
        }
    }

    virtual void on_unset_material()
    {
        if(mDriver->query_feature(EVMF_MULTITEXTURE))
        {
            mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
#ifdef GL_ARB_texture_env_combine
            glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.f);
#else
            glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.f);
#endif
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
            mDriver->extGlActiveTexture(GL_TEXTURE0_ARB);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        }
    }
};

//! detail map  material renderer
class MaterialRenderer_DETAIL_MAP : public MaterialRenderer_C
{
public:

    MaterialRenderer_DETAIL_MAP(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(2);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(material.mMaterialType != lastMaterial.mMaterialType || resetAllRenderstates)
        {
            // diffuse map is default modulated

            // detail map on second layer
            if(mDriver->query_feature(EVMF_MULTITEXTURE))
            {
                mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
#ifdef GL_ARB_texture_env_combine
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD_SIGNED_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
#else
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
                glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD_SIGNED_EXT);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
#endif
            }
        }
    }

    virtual void on_unset_material()
    {
        if(mDriver->query_feature(EVMF_MULTITEXTURE))
        {
            mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
            mDriver->extGlActiveTexture(GL_TEXTURE0_ARB);
        }
    }
};

//! sphere map material renderer
class MaterialRenderer_SPHERE_MAP : public MaterialRenderer_C
{
public:

    MaterialRenderer_SPHERE_MAP(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(1);
        // texture needs to be flipped for OpenGL
        Myth::Math::Matrix4 tmp = mDriver->get_transform(ETS_TEXTURE_0);
        tmp[5]*=-1;
        mDriver->set_transform(ETS_TEXTURE_0, tmp);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(material.mMaterialType != lastMaterial.mMaterialType || resetAllRenderstates)
        {
            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

            glEnable(GL_TEXTURE_GEN_S);
            glEnable(GL_TEXTURE_GEN_T);
        }
    }

    virtual void on_unset_material()
    {
        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
    }
};

//! reflection 2 layer material renderer
class MaterialRenderer_REFLECTION_2_LAYER : public MaterialRenderer_C
{
public:

    MaterialRenderer_REFLECTION_2_LAYER(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(2);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(material.mMaterialType != lastMaterial.mMaterialType || resetAllRenderstates)
        {
            if(mDriver->query_feature(EVMF_MULTITEXTURE))
            {
                mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
#ifdef GL_ARB_texture_env_combine
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
#else
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
                glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
#endif
            }
            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
            glEnable(GL_TEXTURE_GEN_S);
            glEnable(GL_TEXTURE_GEN_T);
        }
    }

    virtual void on_unset_material()
    {
        if(mDriver->query_feature(EVMF_MULTITEXTURE))
        {
            mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        }
        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
        if(mDriver->query_feature(EVMF_MULTITEXTURE))
        {
            mDriver->extGlActiveTexture(GL_TEXTURE0_ARB);
        }
    }
};

//! reflection 2 layer material renderer
class MaterialRenderer_TRANSPARENT_REFLECTION_2_LAYER : public MaterialRenderer_C
{
public:

    MaterialRenderer_TRANSPARENT_REFLECTION_2_LAYER(VideoManager_C* d) :
        MaterialRenderer_C(d)
    {
        //
    }

    virtual void on_set_material(const Material& material, const Material& lastMaterial, bool resetAllRenderstates, MaterialRendererServices* services)
    {
        mDriver->disable_textures(2);
        mDriver->set_basic_render_states(material, lastMaterial, resetAllRenderstates);

        if(material.mMaterialType != lastMaterial.mMaterialType || resetAllRenderstates)
        {
            if(mDriver->query_feature(EVMF_MULTITEXTURE))
            {
                mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
#ifdef GL_ARB_texture_env_combine
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
#else
                glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
                glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
                glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
#endif
            }
            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
            glEnable(GL_TEXTURE_GEN_S);
            glEnable(GL_TEXTURE_GEN_T);

            glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
            glEnable(GL_BLEND);
        }
    }

    virtual void on_unset_material()
    {
        if(mDriver->query_feature(EVMF_MULTITEXTURE))
        {
            mDriver->extGlActiveTexture(GL_TEXTURE1_ARB);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        }
        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
        if(mDriver->query_feature(EVMF_MULTITEXTURE))
        {
            mDriver->extGlActiveTexture(GL_TEXTURE0_ARB);
        }
        glDisable(GL_BLEND);
    }

    //! Returns if the material is transparent.
    virtual bool is_transparent() const
    {
        return true;
    }
};

} // namespace Lynx

#endif // __LYNX_MATERIAL_RENDERER_C_HPP_INCLUDED__

